package edu.gatech.ors.mrsim.core;

import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;

import edu.gatech.ors.mrsim.basictasks.IdleTask;
import edu.gatech.ors.mrsim.basictasks.ReceiveMessage;
import edu.gatech.ors.mrsim.basictasks.SendMessage;
import edu.gatech.ors.mrsim.basictasks.WaitTask;
import edu.gatech.ors.mrsim.calculate.SimTaskTimer;

/**
 * This class represents the simulation of a single module.  It is closely related to Simulator (Simulator is made up of a number of
 * SequentialSimulators).  It contains a queue of SimTasks to be performed.
 * @author tdoneal
 *
 */
public class ModuleSimulator {

    
    // 0th element is the next operation to be performed
    protected List<SimTask> tasks;
    // simulated "current time"
    protected long currTimestamp;
    
    //the containing Simulator instance
    protected Simulator parentSimulator;
    
    //the name of this module
    protected String name;

    public ModuleSimulator(String name) {

	this.name = name;
	
	
	// use linkedlist for faster implementation
   	tasks = new LinkedList<SimTask>();
      }
    
    public void setParentSimulator(Simulator parent){
	this.parentSimulator = parent;
    }
    
    public long getCurrentTimeStamp(){
	return this.currTimestamp;
    }

    public void enqueueTask(SimTask st) {
	// ensure thread safety
	synchronized (tasks) {
	    tasks.add(st);
	}
    }
    

    /**
     * Simulates all tasks currently on the queue
     */
    public void simulateNextTask() {
	// block and wait for the simulator to finish
	new SimulateThread().run();
    }

    
    public String toString(){
	return "ModuleSim("+this.name+")";
    }
    
    /**
     * This method should be overridden to provide module-specific functionality
     * By default, it handles the sending of intermodule packets
     * @param task
     */
    public void handleTaskCompletion(SimTask finishedTask){
	//if the task was to send a interprocess packet
	//send the interprocess packet
	if (finishedTask instanceof SendMessage){
	    SendMessage sp = (SendMessage)finishedTask;
	    String destMod = sp.dest;
	    //get the module to send it to
	    ModuleSimulator destSim = parentSimulator.getModuleSimulatorByID(destMod);
	    if (destSim==null){
		throw new RuntimeException("Non-existent target module: "+destMod);
	    }
	    
	    //if the module is busy, simply
	    //add a receive packet task to that module
	    
	    //if the module is idle, make sure its wait until at the current time before it receives the packet
	    //we can't be sending packets into the past, now, can we?
	    
	    if (destSim.getTasksRemainingCount()>0){
		destSim.enqueueTask(new ReceiveMessage(sp));
	    }else{
		long durToWait = currTimestamp-destSim.currTimestamp;
		if (durToWait<0){
		    throw new RuntimeException("Invalid module-to-module communication");
		}
		//avoid adding an unnecessary 0-idle task
		if (durToWait>0){
		    
		    destSim.enqueueTask(new IdleTask(durToWait));
		}
		destSim.enqueueTask(new ReceiveMessage(sp));
	    }
	}
    }
    
    /**
     * Runs the next task on the queue, or throws exception if no tasks remain
     * 
     * @author tdoneal
     * 
     */
    private class SimulateThread extends Thread {
	public void run() {
	    
	    SimTaskTimer timer = parentSimulator.getTimer();
	    //Logger logger = parentSimulator.getLogger();
	    //logging moved to Simulator class

	    System.out.println("Running a task on simulator: "+name+" using timer: " + timer);
	    
		// if there are tasks left on the queue, pick a task to complete
		boolean tasksLeft = false;
		SimTask currentTask = null;
		synchronized (tasks) {
		    if (tasks.size() > 0) {
			tasksLeft = true;
			currentTask = tasks.get(0);
		    }
		    if (tasksLeft) {
			// now we are "simulating" the task

			 System.out.println("Starting task: " + currentTask
				+ " at time " + currTimestamp);
			//put an event on the priority queue
			//parentSimulator.putEvent(new NamedEvent("Starting task: "+currentTask,ModuleSimulator.this,currTimestamp));
			long taskTime = timer.time(currentTask);
			currTimestamp += taskTime;
			 System.out.println("Finished task: " + currentTask
				+ " at time " + currTimestamp);
			//put an event on the priority queue
			parentSimulator.putEvent(new NamedEvent("Finished "+currentTask,ModuleSimulator.this,currTimestamp));
			// remove task from the queue
			tasks.remove(0);

			// completed tasks may spawn other tasks in future
			// hook for extensible functionality
			handleTaskCompletion(currentTask);
			

		    }// end if (tasksLeft) block
		    else{
			//no more tasks left
			throw new RuntimeException("No more tasks were left");
		    }
		}// end synchronized block

	}// end function
    }// end class

    public String getName() {
	return this.name;
    }


    public void enqueueTasks(List<SimTask> list) {
	for (int i = 0;i<list.size();i++){
	    this.enqueueTask(list.get(i));
	}
    }


    public int getTasksRemainingCount() {
	synchronized (this.tasks){
	    return this.tasks.size();
	}
    }


}
